﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spanner;

namespace SpannerWebSite
{
    class CommonUI: UI
    {
        internal static Html Spanner = A("Spanner").WithAttr("href", "http://spanner.codeplex.com");
        internal static Html Knockout = A("Knockout").WithAttr("href", "http://knockoutjs.com");
        internal static Html TypeScript = A("TypeScript").WithAttr("href", "http://www.typescriptlang.org/");
        internal static Html JQuery = A("jQuery").WithAttr("href", "http://jquery.com");

        internal static Html KnockoutBindingDocs(string binding)
        {
            var href = string.Format("http://knockoutjs.com/documentation/{0}-binding.html", binding);
            return A("Knockout ", Code(binding), " binding").WithAttr("href", href);
        }

        internal static SpannerChapter Chapter(string title, string id, IEnumerable<SpannerSection> sections,
            IEnumerable<Act> initActs)
        {
            return new SpannerChapter
            {
                ID = HtmlID(id),
                Title = title,
                Sections = sections,
                InitActs = initActs
            };
        }

        internal static SpannerSection Section(string title, string id, params Html[] body)
        {
            return new SpannerSection
            {
                ID = HtmlID(id),
                Title = title,
                Content = Group(body)
            };
        }

        internal static Html Subsection(string title, params Html[] body)
        {
            return Div()
                .WithAttr("id", CssClass.Subsection)
                .WithChildren(Group(H3(title), Group(body)));
        }

        internal static Html Subsubsection(string title, params Html[] body)
        {
            return Div()
                .WithAttr("id", CssClass.Subsubsection)
                .WithChildren(Group(H4(title), Group(body)));
        }

        internal static string Esc(string x)
        {
            return System.Security.SecurityElement.Escape(x);
        }

        internal static Html Code(string code)
        {
            return UI.Code(Esc(code));
        }

        internal enum Lang { csharp, markup, css, javascript }

        internal static Html FileFragmentFigure(
            Html caption,
            Lang language,
            string filePath,
            string startPattern,
            string endPattern,
            bool skipFirstLine = false,
            bool ignoreUnindentedLines = true,
            Func<List<string>, List<string>> filter = null )
        {
            var lines = string.Join(
                Environment.NewLine,
                ReadFileFragment(filePath, startPattern, endPattern, skipFirstLine, ignoreUnindentedLines, filter)
            );
            return Figure(
                (caption == null ? Group() : FigCaption(caption)),
                Pre(Code(lines).WithClass("language-" + language))
            );
        }

        internal static List<string> StripSpannerRuntimeFilter(List<string> fragment)
        {
            var result = new List<string> { };
            var foundStart = false;
            var foundEnd = false;
            foreach (var line in fragment) {
                if (foundEnd) {
                    result.Add(line);
                } else if (foundStart) {
                    if (line == "      })();") {
                        foundEnd = true;
                        //result.Add(line);
                    }
                } else if (line == "      // This is the Spanner runtime environment.") {
                    //if (line == "      var spanner = new (function () {") {
                    foundStart = true;
                    result.Add("      [Spanner runtime code omitted for brevity.]");
                }
                else
                {
                    result.Add(line);
                }
            }
            return result;
        }

        /// <summary>
        /// Read a list of lines from a given file, starting with a line containing a
        /// given pattern, and ending with (but not including) a line matching another
        /// given pattern.
        /// 
        /// The common indentation of the lines read is removed, optionally ignoring
        /// for this purpose lines with no indentation.
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="startPattern"></param>
        /// <param name="endPattern"></param>
        /// <returns></returns>
        internal static List<string> ReadFileFragment(
            string filePath,
            string startPattern,
            string endPattern,
            bool skipFirstLine = true,
            bool ignoreUnindentedLines = true,
            Func<List<string>, List<string>> filter = null )
        {
            endPattern = endPattern ?? "!@# THIS SHOULD NEVER MATCH! $%^";
            var fragment = (List<string>) null;
            try
            {
                var lines = File.ReadAllLines(filePath);
                fragment = lines.SkipWhile(x => !x.Contains(startPattern))
                                .TakeWhile(x => !x.Contains(endPattern))
                                .Skip(skipFirstLine ? 1 : 0)
                                .ToList();
                if (fragment.Count == 0)
                    fragment.Add("[" + filePath + " does not contain '" + startPattern + "'.]");
            }
            catch
            {
                throw new ApplicationException(filePath + "not found.");
            }

            fragment = TrimCommonIndentation(fragment, ignoreUnindentedLines);

            if (filter != null) fragment = filter(fragment);

            return fragment;
        }

        /// <summary>
        /// Trim the common indentation from a block of lines, optionally ignoring lines with
        /// no indentation.
        /// </summary>
        /// <param name="lines"></param>
        /// <param name="ignoreUnindentedLines"></param>
        /// <returns></returns>
        internal static List<string> TrimCommonIndentation(
            List<string> lines,
            bool ignoreUnindentedLines = true )
        {
            var n = lines.Where(x => x.Any(y => !Char.IsWhiteSpace(y)))
                         .Where(x => (x.Length != 0 && Char.IsWhiteSpace(x[0])) || !ignoreUnindentedLines)
                         .Select(x => x.TakeWhile(Char.IsWhiteSpace).Count())
                         .Concat(new [] { int.MaxValue })
                         .Min();

            return lines.Select(x => (x.Length == 0 || !Char.IsWhiteSpace(x[0]) ? x : x.Substring(n)))
                        .ToList();
        }
    }
}
